#ifndef __CXXCALL_TESTS_CALLIN__
#define __CXXCALL_TESTS_CALLIN__   1

#include "../cxxcall.hpp"
#include "utils.hpp"
#include <iostream>
#include <type_traits>

namespace callin
{

template <int ...>
struct seq {};

template <int N, int ...S>
struct gens : gens<N - 1, N - 1, S...> {};

template <int ...S>
struct gens<0, S...>
{
  typedef seq<S...> type;
};

/*
 * We need to jump through some hoops to construct a tuple via
 * function calls, because libstdc++ uses reverse order.
 */

template <bool B>
struct setter;

template <int N, class ...Args>
void set_tuple (std::tuple<Args...>& tup, cxxcall::cb_data *data)
{
  const bool B = N < std::tuple_size<std::tuple<Args...> >::value;
  setter<B>::template set<N> (tup, data);
}

template <bool B>
struct setter
{
  template <int N, class ...Args>
  static void set (std::tuple<Args...>& tup, cxxcall::cb_data *data)
    {
      typedef typename std::remove_reference<
        decltype (std::get<N> (tup))>::type type;

      std::get<N>(tup) = data->arg<type> ();
      set_tuple<N + 1> (tup, data);
    }
};

template <>
struct setter<false>
{
  template <int N, class ...Args>
  static void set (std::tuple<Args...>&, cxxcall::cb_data *)
    {
    }
};

template <class Fn, class ...Args>
struct cb_wrapper
{
  typedef cb_wrapper<Fn, Args...> self_type;
  typedef typename std::result_of<Fn (Args...)>::type ret_type;

  Fn fct;
  void *mem;
  std::tuple<Args...> tup_args;

  cb_wrapper (Fn f) : fct (f)
    {
      auto sfn = std::is_same<void, ret_type>::value ? helper_v : helper_x;
      this->mem = cxxcall::cb_data::alloc (sfn, this);
    }

  template <int ...S>
  ret_type call_tuple (seq<S...>)
    {
      return (this->fct (std::get<S>(this->tup_args)...));
    }

  static void
  helper_x (cxxcall::cb_data *data, void *arg)
    {
      auto self = (self_type *)arg;
      data->start<ret_type> ();
      set_tuple<0> (self->tup_args, data);
      global_tuple = &self->tup_args;
      data->ret (self->call_tuple (typename gens<sizeof...(Args)>::type ()));
    }

  static void
  helper_v (cxxcall::cb_data *data, void *arg)
    {
      auto self = (self_type *)arg;
      data->start<void> ();
      set_tuple<0> (self->tup_args, data);
      global_tuple = &self->tup_args;
      self->call_tuple (typename gens<sizeof...(Args)>::type ());
      data->ret ();
    }

  ret_type call (Args... args)
    {
      return (((Fn)this->mem) (args...));
    }

  ~cb_wrapper ()
    {
      cxxcall::cb_data::dealloc (this->mem);
    }
};

template <class ...Args>
void test_sum_helper (Args... args)
{
  typedef typename sum_type<Args...>::type Rtype;
  typedef Rtype (*fn_type) (Args...);

  auto fn = (fn_type)sum<Args...>;
  auto v = cb_wrapper<fn_type, Args...> (fn);
  ASSERT (v.call (args...) == fn (args...));
}

#define test_sum(suffix)   \
  test_sum_helper (CONCAT (42, suffix));   \
  test_sum_helper (CONCAT (-77, suffix), CONCAT (78, suffix));   \
  test_sum_helper (CONCAT (-101, suffix), CONCAT (102, suffix),   \
                   CONCAT (-103, suffix));   \
  \
  test_sum_helper (CONCAT (211, suffix), CONCAT (-212, suffix),   \
                   CONCAT (213, suffix), CONCAT (-214, suffix));   \
  \
  test_sum_helper (CONCAT (306, suffix), CONCAT (-307, suffix),   \
                   CONCAT (308, suffix), CONCAT (-309, suffix),   \
                   CONCAT (310, suffix));   \
  \
  test_sum_helper (CONCAT (-451, suffix), CONCAT (452, suffix),   \
                   CONCAT (-453, suffix), CONCAT (454, suffix),   \
                   CONCAT (-455, suffix), CONCAT (456, suffix));   \
  \
  test_sum_helper (CONCAT (-523, suffix), CONCAT (524, suffix),   \
                   CONCAT (-525, suffix), CONCAT (526, suffix),   \
                   CONCAT (-527, suffix), CONCAT (528, suffix),   \
                   CONCAT (-529, suffix));   \
  \
  test_sum_helper (CONCAT (678, suffix), CONCAT (-679, suffix),   \
                   CONCAT (680, suffix), CONCAT (-681, suffix),   \
                   CONCAT (682, suffix), CONCAT (-683, suffix),   \
                   CONCAT (684, suffix), CONCAT (-685, suffix));   \
  \
  test_sum_helper (CONCAT (7, suffix), CONCAT (-8, suffix),   \
                   CONCAT (9, suffix), CONCAT (-10, suffix),   \
                   CONCAT (11, suffix), CONCAT (-12, suffix),   \
                   CONCAT (13, suffix), CONCAT (-14, suffix),   \
                   CONCAT (15, suffix))

void test_static_sum ()
{
#undef SUFFIX
#define SUFFIX
  test_sum (SUFFIX);

#undef SUFFIX
#define SUFFIX   ll
  test_sum (SUFFIX);

#undef SUFFIX
#define SUFFIX .1f
  test_sum (SUFFIX);

#undef SUFFIX
#define SUFFIX   .1
  test_sum (SUFFIX);

#undef SUFFIX
#define SUFFIX   .1l
  test_sum (SUFFIX);

#undef test_sum
}

test_module callin_tests
{
  "callin",
  {
    { "static callins", test_static_sum }
  }
};

} // namespace callin

#endif
